import asyncio
import abc
import json
import time
import traceback

from playwright.async_api import async_playwright

from models.ziniao_accounts import ziniao_accounts_model
from items.task import TaskItem, RunningModelItem, FailedModelItem, TaskStatus
from models.task import task_model
from utils.ziniao import ZiNiaoBrowserTools
from exceptions import SpiderErrorToEnd, SpiderErrorToRetry
from utils import logger
from utils.fetch_api import FetchAPI


class BaseSpider(abc.ABC):
    def __init__(self, item: TaskItem, **kwargs):
        """

        :param browser_id: 浏览器ID
        :param exe_models: ["shipping_inbounds",...]
        :param model_kwargs: {"shipping_inbounds":{"id":"13734323"}...}
        :param kwargs:
        """
        self.ziniao_tools = ZiNiaoBrowserTools()
        self.ziniao_user_info = None
        self.fetch_api = FetchAPI

        self.browser_id = item.browser_id  # 浏览器ID
        self.exe_models = item.exe_models  # 要执行的模块
        self.item = item
        self.platform = self.item.platform
        self.init_running_model()  # 初始化任务并保存

    def init_running_model(self):
        if len(self.item.failed_models):
            self.item.failed_models = []
        if len(self.item.running_models.keys()) == 0:
            for model, kw_list in self.exe_models.items():
                for kw in kw_list:
                    self.item.running_models.update(
                        {
                            str(int(time.time() * 1000)): RunningModelItem(model_name=model, model_kwargs=kw)
                        }
                    )
                    time.sleep(0.001)
            self.item.status = TaskStatus.running
            task_model.sync_update_one_by_id(self.item.id, self.item)

    @abc.abstractmethod
    async def login(self):
        ...

    async def get_ziniao_account(self) -> str:
        if not self.platform:
            raise ValueError("类的平台未赋值")
        item = await ziniao_accounts_model.async_find_one({"platform": self.platform})
        return item.json(include={"company", "username", "password"})

    async def start_browser(self):
        self.ziniao_user_info = await self.get_ziniao_account()
        for i in range(10):
            try:
                self.start_browser_info = self.ziniao_tools.start_browser(self.ziniao_user_info, self.browser_id)[0]
            except:
                logger.exception("启动紫鸟浏览器异常")
                raise SpiderErrorToEnd("启动紫鸟浏览器异常")
            else:
                if not self.start_browser_info:
                    raise SpiderErrorToEnd("获取紫鸟启动信息失败")
                debugging_port = self.start_browser_info.get('debuggingPort')

                if debugging_port:
                    self.ws_url = self.ziniao_tools.get_web_socket_debugger_url(port=debugging_port)
                    break
                else:
                    self.ziniao_tools.stop_browser(self.ziniao_user_info, self.browser_id)
                    continue
        else:
            raise SpiderErrorToRetry("尝试10次启动店铺失败")

    async def connect_ziniao(self):
        chromium = self.playwright.chromium  # or "firefox" or "webkit".
        self.browser = await chromium.connect_over_cdp(self.ws_url)
        self.context = self.browser.contexts[0]
        self.req = self.context.request
        self.page = self.context.pages[0]

    async def main(self):
        try:
            await self.start_browser()
            async with async_playwright() as playwright:
                self.playwright = playwright
                await self.connect_ziniao()
                await self.login()
                async with asyncio.TaskGroup() as tg:
                    for id, model in self.item.running_models.items():
                        func = getattr(self, model.model_name)
                        task: asyncio.tasks.Task = tg.create_task(func(**model.model_kwargs), name=id)
                        task.add_done_callback(self.task_callback)
        except SpiderErrorToEnd as s:
            self.item.status = TaskStatus.failed
            self.item.message = str(s)
        except BaseExceptionGroup as be:
            logger.exception("异步任务内异常~！")
            logger.error(traceback.format_exc())
            self.item.retry_times += 1
            self.item.status = TaskStatus.ready_to_run
        except Exception as e:
            self.item.status = TaskStatus.ready_to_run
            self.item.message = str(e)
            self.item.retry_times += 1
        else:
            logger.info(f"failed:{self.item.failed_models}")
            logger.info(f"running:{self.item.running_models}")
            if self.ziniao_user_info:
                self.ziniao_tools.stop_browser(self.ziniao_user_info, self.browser_id)
            if len(self.item.failed_models) > 0:
                self.item.message = "部分模块运行失败"
                if len(self.item.running_models) > 0:
                    self.item.retry_times += 1
                    self.item.status = TaskStatus.ready_to_run
                else:
                    self.item.status = TaskStatus.failed
            elif len(self.item.running_models) > 0:
                self.item.retry_times += 1
                self.item.message = "还有模块没有运行完成"
                self.item.status = TaskStatus.ready_to_run
            else:
                self.item.message = "采集完成"
                self.item.status = TaskStatus.success
        finally:
            task_model.sync_update_one_by_id(self.item.id, self.item)

    def task_callback(self, fut: asyncio.tasks.Task):
        model_id = fut.get_name()
        esc = fut.exception()
        if esc:
            if isinstance(esc, SpiderErrorToEnd):
                model = self.item.running_models.pop(model_id, None)
            else:
                model = self.item.running_models.get(model_id, None)
            self.item.failed_models.update({model_id: FailedModelItem(
                model_name=model.model_name,
                model_kwargs=model.model_kwargs,
                message=str(esc)
            )})
        else:
            self.item.running_models.pop(model_id, None)
